import { ShapeFlags } from "../shared"
import { createComponentInstance } from "./component";
import {
    hostCreateElement, 
    hostSetElementText,
    hostPatchProp,
    hostInsert,
    hostRemove
} from "./render-api";
import {queueJob} from "./scheduler"
import {effect} from "@vue/reactivity"
import {h} from "./h"

export const render = (vnode, container) => {
    //调用patch设置属性
    patch(null, vnode, container)
}

function patch(n1, n2, container = null){
    // 基于n2的类型来判断, 因为n2是 vnode
    const { type, shapeFlag } = n2;
    switch(type){
        case "text":
            // TODO
            break;
        // 这里还有其他的一些类型，如: static, fragment, comment
        default:
            // 基于shapeFlag来进行处理
            if(shapeFlag & ShapeFlags.ELEMENT){
                // 处理element
                processElement(n1, n2, container)
            } else if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
                // 处理componnent
                processComponent(n1, n2, container)
            }
    }
}

// 处理element节点
function processElement(n1, n2, container){
    if(!n1){
        mountElement(n2, container)
    }else{
        // TODO
        updateElement(n1, n2, container)
    }
}

// 更新element
function updateElement(n1, n2, containere){
    const oldProps = (n1 && n1.props) || {};
    const newProps = n2.props || {};
    // 应该更新 element， 把el挂载到新的 vnode
    const el = (n2.el = n1.el);
    // 对比props
    patchProps(el, oldProps, newProps);
    // 对比chaildren
    patchChildren(n1, n2, el)
}

// 比对props
function patchProps(el, oldProps, newProps){
    // 对比props有以下几种情况
    // 1. oldProps存在，newProps也存在， 但是变更， 则更新以newProps为准
    for(const key in newProps){
        const prevProp = oldProps[key];
        const nextProp = newProps[key];
        if(prevProp !== nextProp){
            // 对比属性， 更新操作交由hostPatchProp进行
            hostPatchProp(el, key, prevProp, nextProp)
        }
    }
    // 2. oldProps存在，而newProps不存在,此时应该以oldProp作为基准，在newProps没有了
    // 当newprops中存在这个key时，说明在newProps里面已经处理过了，就不需要再处理了
    for(const key in oldProps){
        const prevProp = oldProps[key];
        const nextProp = null;
        if(!(key in newProps)){
            // 这里以 oldProps为基准来遍历， 而且得到的值为newProps内没有的
            // 所以交给 host 更新的时候，把新的值设置为null
            hostPatchProp(el, key, prevProp, nextProp);
        }
    }
}

// 对比children
function patchChildren(n1, n2, container) {
    const {shapeFlag: prevShapeFlag, children: c1} = n1;
    const {shapeFlag, children: c2} = n2;
    // 如果n2的children 是 text 类型的话，就看看和之前的n1的children是否一样
    // 如果不一样就直接重新设置 text
    if(shapeFlag & ShapeFlags.TEXT_CHILDREN){
        if(c2 !== c1){
            // 类型为text_children， 需要更新
            hostSetElementText(container, c2 as string)
        }
    } else {
        // 如果之前是array_children, 现在还是arr_children,那么就需要对比两个children
        if(prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
            if(shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
                patchKeyedChildren(c1, c2, container)
            }
        }
    }
}

// diff比对
function patchKeyedChildren(c1: any[], c2: any[], container){
    let i = 0;
    let e1 = c1.length - 1;
    let e2 = c2.length - 1;
    // 比较两个节点是否相同
    const isSameVNodeType = (n1, n2) => {
        return n1.type === n2.type && n1.key === n2.key;
    }
    while(i <= e1 && i < e2){
        // 从左往右
        const prevChild = c1[i];
        const nextChild = c2[i];
        // 两个child不等(从左往右比对)
        if(!isSameVNodeType(prevChild, nextChild)){
            break;
        }
        // 如果两个节点相等，则递归比较两个节点的属性进行更新
        patch(prevChild, nextChild, container)
        i++;
    }
    while(i <= e1 && i <= e2){
        // 从右向左取值
        const prevChild = c1[e1];
        const nextChild = c2[e2];
        if(!isSameVNodeType(prevChild, nextChild)){
            // 两个child不等
            break;
        }
        // 两个child相等
        patch(prevChild, nextChild, container)
        e1--;
        e2--;
    }
    if(i > e1 && i <= e2){
        // 如果是这种情况就说明 e2 也就是新节点的数量大于旧节点的数量
        // 及新增了节点,此时需要循环c2
        while(i <= e2) {
            //创建新的vnode
            patch(null, c2[i], conatiner)
            i++;
        }
    }else if(i > e2 && i <= e1){
        // 这种情况说明节点的数量减少了，就需要删除多余的节点
        while(i <= e1){
            hostRemove(c1[i].el);
            i++;zu
        }
    }else{
        // 左右两边的对比完毕后，剩下的就是中间的顺序变动的节点
        let s1 = i;
        let s2 = i;
        const keyToNewIndexMap = new Map();
        for(i = s2; i <= e2; i++){
            const nextChild = c2[i];
            keyToNewIndexMap.set(nextChild.key, i);
        }
        // 需要处理的新节点的数量
        const toBePatched = e2 - s2 + 1;
        // 源码中使用0进行初始化，但是有可能 0 是个正常值
        // 所以这里使用-1进行初始化
        const newIndexToOldIndexMap = new Array(toBePatched).fill(-1);
        // 遍历老节点：
        // 1. 老节点中有的，新节点中没有的 -> 删除该节点
        // 2. 新老节点都有的 -> patch节点属性
        for(i = s1; i <= e1; i++){
            const prevChild = c1[i];
            const newIndex = keyToNewIndexMap.get(prevChild.key);
            newIndexToOldIndexMap[newIndex] = i;
            // 因为有可能 nextIndex 的值为0， 所以需要通过值是否为undefined来判断
            // 不能直接使用 if(newInex)来判断
            if(newIndex === undefined){
                // 当前节点的key不存在于newChildren中，需要删除当前节点
                hostRemove(prevChild.el);
            }else{
                // 新老节点都存在
                patch(prevChild, c2[newIndex], container)
            }
        }

        // 遍历新节点:
        // 1. 找出老节点中没有的节点, 而在新节点中存在 -> 添加该节点
        // 2. 最后需要移动以下位置的节点
        for(i = e2; i >= s2; i--){
            const nextChild = c2[i];
            if(newIndexToOldIndexMap[i] === -1){
                // 说明是新增节点:
                patch(null, c2[i],container);
            }else{
                // 有可能i+1没有元素了，所以就需要设置为null
                // 再hostInsert 函数中内没有发现是 null 的话，直接添加到父级容器内
                const anchor = i + 1 >= e2 + 1 ? null : c2[i + 1];
                hostInsert(nextChild.el, container, anchor && anchor.el)
            }
        }
    }
}



// 挂载节点
function mountElement(vnode, container){
    const {shapeFlag, props} = vnode;
    // 1. 先创建element
    // 基于可扩展的渲染api, 保存当前真实DOM节点实例
    const el = (vnode.el = hostCreateElement(vnode.type));
    // 支持单子组件和多子组件的创建
    if(shapeFlag & ShapeFlags.TEXT_CHILDREN){
        // 处理文本:
        hostSetElementText(el, vnode.children);
    }else if(shapeFlag & ShapeFlags.ARRAY_CHILDREN){
        // 这里的children是一个数组，需要一次调用 patch 递归来处理
        mountChildren(vnode.children, el);
    }

    // 处理props
    if(props){
        for(const key in props){
            // TODO:
            // 需要过滤掉vue自身使用的key
            // 与生命周期相关的key: beforeMount, Mounted等
            const nextVal = props[key];
            hostPatchProp(el, key, null, nextVal);
        }
    }

    // 此时可以开始触发生命周期钩子: beforeMount()
    console.log("beforeMount触发")

    // 插入节点:
    hostInsert(el, container);

    // todo: 
    // 触发mounted()钩子:
    console.log("mounted函数触发")
}

// 处理子节点:
function mountChildren(children, container){
    children.forEach((VNodeChild) => {
        // todo: 
        // 这里应该需要处理一些vnodeChild
        // 因为其有可能不是vnode类型
        patch(null, VNodeChild, container)
    })
}

function processComponent(n1, n2, container){
    // 如果n1没有值的话，那么就是mount
    if(!n1){
        // 初始化component
        mountComponent(n2, container)
    }else{
        // todo:
        // updateComponent
    }
}

// 挂载组件
function mountComponent(initialVNode, container){
    // 创建一个 component instance
    const instance = (initialVNode.component = createComponentInstance(initialVNode));
    // 给 instance 加工
    setupComponent(instance)
    
    setupRenderEffect(instance, container);
}

// 加工处理组件
function setupComponent(instance){
    // 1. 处理props:
    initProps();

    // 2. 处理 slots:
    initSlots();

    // 源码中有两种类型的component
    // 一种是基于options创建的
    // 一种是基于function的
    // 这里处理的是 options创建的
    // 叫做 stateful类型
    setupStatefulComponent(instance)
}

// 初始化 props:
function initProps(){
    // TODO:
}

// 初始化Slots:
function initSlots(){
    // TODO:
}

function setupStatefulComponent(instance) {
    // TODO:
    // 1. 先创建代理 proxy:

    // 2. 调用setup:
    // TODO:
    // setup应该传入 props 和 setupContext
    const setupResult = instance.setup && instance.setup(instnace.props)

    // 处理setupResult
    handleSetupResult(instance, setupResult);
}


// 处理setup返回结果:
function handleSetupResult(instance, setupResult) {
    // setup 返回值不同，会有不同的处理
    // 1. 看看setupResult的类型是什么
    if(typeof setupResult === "function"){
        // 如果返回的是一个 function, 那么就绑定到render上
        // 认为是 render 的逻辑
        instance.render = setupResult;
    }else if(typeof setupResult === "object"){
        // 返回的一个对象则先保存到setupState上
        instance.setupState = setupResult;
    }

    finishComponentSetup(instance)
}

function finishComponentSetup(instance){
    // 给 instance 设置 render:

    // 先取到用户设置的component options：
    const Component = instance.type;
    if(!instance.render){
        // TODO:
        // 调用 compile 模板来编译 template:
        // Component.render = compile(Component.template, {
        //     isCustomElement: instance.appContext.config.isCustomElement || NO
        //   })
    }
    instance.render = Component.render;
}

function applyOptions(){
    // 兼容 vue2.x
    // TODO:
}

function setupRenderEffect(instance, container){
    // 调用render
    // 应该传入 ctx 也就是 proxy：
    // ctx 可以选择暴露给用户的 api
    // 源码里面调用的是 renderComponent 函数:
    // 这里简化为直接调用 render:

    // TODO:
    // 搜集数据改变后执行的处理
    // 依赖搜集
    // 触发依赖
    instance.update = effect(
        function componentEffect(){
            if(!instance.isMounted){
                // 组件初始化的时候回执行这里
                // 为什么要在这里调用 render 函数
                // 是因为再 effect 内调用 render 才能触发依赖搜集:
                // 等到后面响应式的值改变后回再次触发这个函数:
                const subTree = (instance.subTree = instance.render(instance.proxy));

                // TODO:
                // 触发该组件的beforeMount
                
                // 这里基于subTree 再次调用 patch
                // 基于render 返回 vnode, 再次进行渲染
                // 这里将这个行为隐喻为开箱
                // 一个组件就是一个箱子
                // 里面有可能是element （也就是可以直接渲染）
                // 也可能还是component
                // 这里的递归的开箱:
                // 而 subTree 就是当前的这个箱子里装的东西
                // 箱子(组件)只是一个概念，它实际是不需要渲染的
                // 要渲染的箱子里面的subTree
                patch(null, subTree, container)

                // 触发 mounted钩子

                instance.isMounted = true;
            }else{
                // 响应式的值变更后回从这里执行逻辑
                // 主要就是拿到新的vnode, 然后和之前的 vnode 进行对比

                // 拿到最新的 subTree
                const nextTree = instance.render(instance.proxy)
                // 替换之前的subTree
                const prevTree = instance.subTree;
                instance.subTree = nextTree;

                // 触发beforeUpdated hook

                // 用旧的 vnode 和新的 vnode 交给 Patch 来进行处理
                patch(prevTree, nextTree, prevTree.el)

                // 触发 updated hook
            }
        },
        {
            scheduler: (effect) => {
                // 将effect推到微任务的时候执行
                queueJob(effect)
            }
        }
    )
}